文章同步發表至 Medium
根據前一篇確定好檔案的座標系統之後,我們就來正式匯入資料吧。
我們可以根據政府資料開放平臺上,景點的頁面中所提供的觀光資訊標準格式,把手上的 Shapefile 內容過濾出我們所需要的資訊,存入資料庫。
在建立之前,記得先把 PostGIS 引入,才能正確的使用 geometry
的型別喔:
-- 引入 PostGIS
CREATE EXTENSION postgis;
-- 建立 table,以景點為例
CREATE TABLE public.scenic_spot
(
id character varying NOT NULL, -- 代碼
name character varying NOT NULL, -- 名稱
telephone character varying, -- 電話
address character varying, -- 地址
county character varying, -- 縣市
town character varying, -- 鄉鎮市區
x double precision NOT NULL, -- 主要出入口或主要建築物所在位置的經度
y double precision NOT NULL, -- 主要出入口或主要建築物所在位置的緯度
type integer, -- 景點分類
park character varying, -- 停車位資訊
info character varying, -- 參觀資訊
update_time timestamp without time zone, -- 最後異動時間
geom geometry NOT NULL,
PRIMARY KEY (id)
);
跟資料庫的溝通我選擇使用 EntityFrameworkCore 的方式,因此要進行的步驟總共有三個:
打開 Terminal 的介面(Rider 快捷鍵: Ctrl+Alt+1
),移動到 .csproj
的同一層資料夾底下,輸入指令:
dotnet ef dbcontext scaffold "Host=localhost;Database=tourism;Username=postgres;Password=0000;" Npgsql.EntityFrameworkCore.PostgreSQL -o ./Models/Db -c TourismEntity -f
相關的參數前幾篇有解釋過了,這裡就不再贅述。要注意的是,如果沒有特別指定 -f
的話,在執行的時候可能會出現錯誤:
The following file(s) already exist in directory 'C:\Users\user\Documents\GitHub\tourism-import\tourism-import\Models\Db': TourismEntity.cs,ScenicSpot
.cs. Use the Force flag to overwrite these files.
using Microsoft.Extensions.Configuration;
using NetTopologySuite.IO.Esri;
using tourism_import;
using tourism_import.Models.Db;
var shapefilePath = @"shapefile path";
var scenicSpots = new List<ScenicSpot>();
var features = Shapefile.ReadAllFeatures(shapefilePath);
var scenicSpots = features.Select(f =>
{
var attributes = f.Attributes.GetValues();
return new ScenicSpot
{
Id = attributes[(int)EnumAttributeName.Id].ToString(),
Name = attributes[(int)EnumAttributeName.Name].ToString(),
Telephone = attributes[(int)EnumAttributeName.Tel].ToString(),
Address = attributes[(int)EnumAttributeName.Add].ToString(),
County = attributes[(int)EnumAttributeName.Region].ToString(),
Town = attributes[(int)EnumAttributeName.Town].ToString(),
X = attributes[(int)EnumAttributeName.Px].ToDouble(),
Y = attributes[(int)EnumAttributeName.Py].ToDouble(),
Type = attributes[(int)EnumAttributeName.Orgclass].ToString(),
Park = attributes[(int)EnumAttributeName.Parkin_nfo].ToString(),
Info = attributes[(int)EnumAttributeName.Ticketinfo].ToString(),
UpdateTime = attributes[(int)EnumAttributeName.Changetime].ToDateTime(),
Geom = f.Geometry,
};
}).ToList();
var db = new TourismEntity();
await db.ScenicSpots.AddRangeAsync(scenicSpots);
await db.SaveChangesAsync();
// Shapefile 欄位對應位置
public enum EnumAttributeName
{
Id = 0, // 代碼
Name = 1, // 名稱
Tel = 3, // 電話
Add = 4, // 地址
Region = 6, // 縣市
Town = 7, // 鄉鎮市區
Px = 16, // 主要出入口或主要建築物所在位置的經度
Py = 17, // 主要出入口或主要建築物所在位置的緯度
Orgclass = 18, // 景點分類
Parkin_nfo = 24, // 停車位資訊
Ticketinfo = 27, // 參觀資訊
Changetime = 29, // 最後異動時間
}
因為利用 features.Attributes.GetValues()
所讀取出來的資料都是文字,所以我們需要在第 21 行和第 22 行使用自定義的 ToDouble()
和第 26 行的 ToDateTime()
,轉成先前資料庫所定義的格式。
public static class Extensions
{
public static double ToDouble(this object attr)
{
return double.Parse(attr.ToString());
}
public static DateTime? ToDateTime(this object attr)
{
if (string.IsNullOrEmpty(attr.ToString())) return null;
var dateTimeString = (attr.ToString()).Split('+')[0];
var dateString = dateTimeString.Split('T')[0];
var timeString = dateTimeString.Split('T')[1];
var year = int.Parse(dateString.Split('-')[0]);
var month = int.Parse(dateString.Split('-')[1]);
var day = int.Parse(dateString.Split('-')[2]);
var hour = int.Parse(timeString.Split(':')[0]);
var minute = int.Parse(timeString.Split(':')[1]);
var second = int.Parse(timeString.Split(':')[2]);
return new DateTime(year, month, day, hour, minute, second);
}
}
執行完成之後可以利用 pgAdmin 檢視一下資料: